Skip to content

Dev -> Main#96

Merged
chrisdrackett merged 158 commits into
mainfrom
dev
Jan 11, 2026
Merged

Dev -> Main#96
chrisdrackett merged 158 commits into
mainfrom
dev

Conversation

@chrisdrackett

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings January 11, 2026 05:48
try {
const biomePath = join(process.cwd(), "biome.json")
await execAsync(
`yarn biome check --write --unsafe --config-path=${biomePath} ${shadowPath}`,

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 6 months ago

In general, dynamically constructed shell commands that include environment- or filesystem-derived values should avoid passing a single interpolated string to a shell. Instead, call the executable directly and pass arguments in an array using APIs like execFile/spawn (or execFileSync/spawnSync), so the shell does not interpret special characters in paths or other data.

For this specific case, the best fix is to stop using execAsync (based on exec) for the Biome formatting step, and instead invoke yarn (or npx/biome) with an explicit argument list. We can add a new promisified helper built from execFile (e.g., execFileAsync) and use it just for this call. That preserves existing behavior—calling yarn biome check --write --unsafe --config-path=... <shadowPath>—but avoids shell interpolation issues. Concretely in packages/paradigm/scripts/syncFigma.ts:

  • Import execFile in addition to exec from node:child_process.
  • Define const execFileAsync = promisify(execFile) alongside execAsync.
  • Replace the execAsync(\yarn biome check ...`)call withexecFileAsync("yarn", ["biome", "check", "--write", "--unsafe", `--config-path=${biomePath}`, shadowPath])`.

No other logic needs to change, and the same Biome formatting is applied to the generated shadows.ts file.

Suggested changeset 1
packages/paradigm/scripts/syncFigma.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/paradigm/scripts/syncFigma.ts b/packages/paradigm/scripts/syncFigma.ts
--- a/packages/paradigm/scripts/syncFigma.ts
+++ b/packages/paradigm/scripts/syncFigma.ts
@@ -1,4 +1,4 @@
-import { exec } from "node:child_process"
+import { exec, execFile } from "node:child_process"
 import { mkdir, rm, writeFile } from "node:fs/promises"
 import { dirname, join } from "node:path"
 import { fileURLToPath } from "node:url"
@@ -9,6 +9,7 @@
 import { letterCase } from "../src/components/Text/letterCase.js"
 
 const execAsync = promisify(exec)
+const execFileAsync = promisify(execFile)
 const __dirname = dirname(fileURLToPath(import.meta.url))
 
 // Load environment variables from .env file
@@ -375,9 +376,14 @@
 		// Format shadows with Biome
 		try {
 			const biomePath = join(process.cwd(), "biome.json")
-			await execAsync(
-				`yarn biome check --write --unsafe --config-path=${biomePath} ${shadowPath}`,
-			)
+			await execFileAsync("yarn", [
+				"biome",
+				"check",
+				"--write",
+				"--unsafe",
+				`--config-path=${biomePath}`,
+				shadowPath,
+			])
 			console.log("✓ Formatted shadows with Biome")
 		} catch (_error) {
 			console.warn("⚠ Biome formatting failed for shadows, but continuing...")
EOF
@@ -1,4 +1,4 @@
import { exec } from "node:child_process"
import { exec, execFile } from "node:child_process"
import { mkdir, rm, writeFile } from "node:fs/promises"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
@@ -9,6 +9,7 @@
import { letterCase } from "../src/components/Text/letterCase.js"

const execAsync = promisify(exec)
const execFileAsync = promisify(execFile)
const __dirname = dirname(fileURLToPath(import.meta.url))

// Load environment variables from .env file
@@ -375,9 +376,14 @@
// Format shadows with Biome
try {
const biomePath = join(process.cwd(), "biome.json")
await execAsync(
`yarn biome check --write --unsafe --config-path=${biomePath} ${shadowPath}`,
)
await execFileAsync("yarn", [
"biome",
"check",
"--write",
"--unsafe",
`--config-path=${biomePath}`,
shadowPath,
])
console.log("✓ Formatted shadows with Biome")
} catch (_error) {
console.warn("⚠ Biome formatting failed for shadows, but continuing...")
Copilot is powered by AI and may make mistakes. Always verify output.
const biomePath = join(process.cwd(), "biome.json")
const iconPath = join(process.cwd(), "src/components/Icon")
await execAsync(
`yarn biome check --write --unsafe --config-path=${biomePath} ${iconPath}`,

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 6 months ago

General fix: Avoid constructing a shell command string with interpolated paths and executing it via a shell (exec). Instead, invoke the command with arguments passed as an array to a non-shell-spawning API such as execFile/spawn, or, since we already use execAsync only here, replace these calls with a wrapper around execFile (or use npx/yarn via arguments). This prevents the shell from interpreting special characters in paths.

Best concrete fix here:

  • Stop using execAsync (based on exec) for the Biome invocations.
  • Introduce a small helper using spawn from node:child_process that:
    • Invokes yarn directly with arguments ["biome", "check", "--write", "--unsafe", "--config-path", biomePath, targetPath].
    • Sets stdio: "inherit" so output appears normally.
    • Returns a Promise<void> that resolves on exit code 0 and rejects otherwise.
  • Use this helper in both places where Biome is currently invoked:
    • Around line 378–380 for shadowPath.
    • Around line 456–460 for iconPath.
  • Keep the rest of the logic and error handling intact.

Specifics:

  • In packages/paradigm/scripts/syncFigma.ts:
    • Replace the exec import with spawn (no need for promisify(exec) anymore).
    • Remove execAsync definition.
    • Add a runBiome helper function near the top (after env/config setup is fine).
    • Replace the template-literal execAsync(...) calls with await runBiome(biomePath, shadowPath) and await runBiome(biomePath, iconPath) respectively.

No change in user-visible functionality: we still run the exact same Biome checks on the same paths with the same options, but without passing the full command through a shell.


Suggested changeset 1
packages/paradigm/scripts/syncFigma.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/paradigm/scripts/syncFigma.ts b/packages/paradigm/scripts/syncFigma.ts
--- a/packages/paradigm/scripts/syncFigma.ts
+++ b/packages/paradigm/scripts/syncFigma.ts
@@ -1,4 +1,4 @@
-import { exec } from "node:child_process"
+import { spawn } from "node:child_process"
 import { mkdir, rm, writeFile } from "node:fs/promises"
 import { dirname, join } from "node:path"
 import { fileURLToPath } from "node:url"
@@ -8,9 +8,29 @@
 
 import { letterCase } from "../src/components/Text/letterCase.js"
 
-const execAsync = promisify(exec)
 const __dirname = dirname(fileURLToPath(import.meta.url))
 
+async function runBiome(configPath: string, targetPath: string): Promise<void> {
+	return await new Promise((resolve, reject) => {
+		const child = spawn("yarn", ["biome", "check", "--write", "--unsafe", `--config-path=${configPath}`, targetPath], {
+			stdio: "inherit",
+			shell: false,
+		})
+
+		child.on("error", (error) => {
+			reject(error)
+		})
+
+		child.on("exit", (code) => {
+			if (code === 0) {
+				resolve()
+			} else {
+				reject(new Error(`Biome exited with code ${code}`))
+			}
+		})
+	})
+}
+
 // Load environment variables from .env file
 config()
 
@@ -375,9 +393,7 @@
 		// Format shadows with Biome
 		try {
 			const biomePath = join(process.cwd(), "biome.json")
-			await execAsync(
-				`yarn biome check --write --unsafe --config-path=${biomePath} ${shadowPath}`,
-			)
+			await runBiome(biomePath, shadowPath)
 			console.log("✓ Formatted shadows with Biome")
 		} catch (_error) {
 			console.warn("⚠ Biome formatting failed for shadows, but continuing...")
@@ -455,9 +471,7 @@
 		try {
 			const biomePath = join(process.cwd(), "biome.json")
 			const iconPath = join(process.cwd(), "src/components/Icon")
-			await execAsync(
-				`yarn biome check --write --unsafe --config-path=${biomePath} ${iconPath}`,
-			)
+			await runBiome(biomePath, iconPath)
 			console.log("✓ Formatted and organized imports")
 		} catch (error) {
 			console.warn("⚠ Biome formatting failed, but continuing...")
EOF
@@ -1,4 +1,4 @@
import { exec } from "node:child_process"
import { spawn } from "node:child_process"
import { mkdir, rm, writeFile } from "node:fs/promises"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
@@ -8,9 +8,29 @@

import { letterCase } from "../src/components/Text/letterCase.js"

const execAsync = promisify(exec)
const __dirname = dirname(fileURLToPath(import.meta.url))

async function runBiome(configPath: string, targetPath: string): Promise<void> {
return await new Promise((resolve, reject) => {
const child = spawn("yarn", ["biome", "check", "--write", "--unsafe", `--config-path=${configPath}`, targetPath], {
stdio: "inherit",
shell: false,
})

child.on("error", (error) => {
reject(error)
})

child.on("exit", (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Biome exited with code ${code}`))
}
})
})
}

// Load environment variables from .env file
config()

@@ -375,9 +393,7 @@
// Format shadows with Biome
try {
const biomePath = join(process.cwd(), "biome.json")
await execAsync(
`yarn biome check --write --unsafe --config-path=${biomePath} ${shadowPath}`,
)
await runBiome(biomePath, shadowPath)
console.log("✓ Formatted shadows with Biome")
} catch (_error) {
console.warn("⚠ Biome formatting failed for shadows, but continuing...")
@@ -455,9 +471,7 @@
try {
const biomePath = join(process.cwd(), "biome.json")
const iconPath = join(process.cwd(), "src/components/Icon")
await execAsync(
`yarn biome check --write --unsafe --config-path=${biomePath} ${iconPath}`,
)
await runBiome(biomePath, iconPath)
console.log("✓ Formatted and organized imports")
} catch (error) {
console.warn("⚠ Biome formatting failed, but continuing...")
Copilot is powered by AI and may make mistakes. Always verify output.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR represents a major architectural shift from Expo/React Native to Vite/React, transforming the client application while enhancing server capabilities and introducing a comprehensive UI component library (Paradigm). The changes modernize the development workflow, improve type safety, and add real-time log streaming capabilities.

Changes:

  • Migrated client from Expo to Vite with Monaco editor integration and improved TypeScript support
  • Added Paradigm UI component library with Storybook, Tamagui integration, and Figma sync tooling
  • Enhanced server with SSE log streaming, improved error handling, and expanded automation schema (draft/icon fields)

Reviewed changes

Copilot reviewed 136 out of 419 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
package.json Updated to version 25.10.1, simplified scripts to use workspace commands, updated Node/Yarn versions
apps/client/* Complete rewrite from Expo to Vite with new store architecture, Monaco editor, and routing via Nuqs
apps/server/src/app/services/logger.service.mts Added SSE log streaming with subscription support and improved filtering
apps/server/src/coordinator/services/execute.service.mts Added formatAutomationContext for consistent logging, improved transpile caching
apps/server/src/database/* Added draft and icon fields to automation schema across all database types
apps/server/src/http/services/static.service.mts Enhanced static file serving with better path resolution and SPA routing
packages/paradigm/* New comprehensive UI component library with Button, Icon components, Storybook setup, and Figma sync
Dockerfile Simplified to single-stage build, improved native module handling, better UTF-8 support
.github/workflows/* Streamlined workflows with dev/main branch builds and concurrency controls

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// if we have a value we can assume the model has been edited.
if (currentAutomationRef.current) {
currentAutomationRef.current.setIsEditied(true)

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'setIsEditied' to 'setIsEdited'.

Copilot uses AI. Check for mistakes.
// update the edited state using data and save the draft
// TODO Move this into the store as a derived state
if (currentAutomationState) {
currentAutomationState.setIsEditied(

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'setIsEditied' to 'setIsEdited'.

Copilot uses AI. Check for mistakes.
Comment thread apps/client/src/store/automation.ts Outdated
},
})
.actions({
setIsEditied(isEdited: boolean) {

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'setIsEditied' to 'setIsEdited'.

Suggested change
setIsEditied(isEdited: boolean) {
setIsEdited(isEdited: boolean) {

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +89
} catch (err) {
console.error("[useAutomationLogs] Failed to parse event:", err, event)
}

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error directly in production code. Consider using a proper logging service or error boundary for consistent error handling across the application.

Copilot uses AI. Check for mistakes.
Comment thread apps/client/index.html
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script points to '/src/main.jsx' but the actual main file is at '/src/main.tsx' (TypeScript). This will cause the application to fail to load.

Suggested change
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +28
if (error && typeof error === "object" && "status" in error) {
logData.code = (error as { status: string | number }).status;
}

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error status could be either string or number, but logData.code type might not accept both. Consider normalizing to a single type (e.g., always convert to number) for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +48
if (typeof out === "function") {
out();
}

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for 'typeof out === "function"' seems defensive but the original code assumed 'out' would be called directly. This suggests uncertainty about the return type. Consider adding explicit typing for the 'out' variable to clarify its expected type.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +65
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data)

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.parse errors are caught but not logged with context about what failed to parse. Add logging to help debug malformed server responses.

Copilot uses AI. Check for mistakes.
@chrisdrackett chrisdrackett merged commit 1c5731f into main Jan 11, 2026
4 checks passed
@chrisdrackett chrisdrackett deleted the dev branch January 11, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants